package com.gree.flowable.app.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.gree.common.utils.RedisUtil;
import com.gree.flowable.app.mapper.ActDeModelMapper;
import com.gree.flowable.app.pojo.ex.FlwExNodeset;
import com.gree.flowable.app.pojo.ex.ModelExUser;
import com.gree.flowable.app.pojo.model.*;
import com.gree.flowable.app.pojo.vo.DeployElementVO;
import com.gree.flowable.app.pojo.vo.ModelExVO;
import com.gree.flowable.app.pojo.vo.ReturnVO;
import com.gree.flowable.app.services.IFlowableBpmnModelService;
import com.gree.flowable.app.services.IFlowableModelService;
import com.gree.flowable.app.tools.constants.ReturnCode;
import com.gree.framework.utils.SecurityUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.converter.UserTaskXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.ui.common.service.exception.BadRequestException;
import org.flowable.ui.common.service.exception.ConflictingRequestException;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.model.ModelKeyRepresentation;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.*;

@RestController

@Api(tags = "流程模型管理API")
@RequestMapping("/rest/model")
public class IFlowableModelApi extends FlowableBaseApi {
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ModelService modelService;

    @Autowired
    private IFlowableBpmnModelService iFlowableBpmnModelService;

    @Autowired
    private IFlowableModelService iFlowableModelService;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * @return
     * @Title: 发布流程模型
     */
    @GetMapping("/publish/{modelId}")
    @ApiOperation("发布流程模型")
    @ApiImplicitParam(name = "modelId", value = "流程模板ID", dataType = "String")
    public ReturnVO<String> deploymentDefinition(@PathVariable String modelId) {
        ReturnVO returnVO = null;
        try {
            Model model = modelService.getModel(modelId);

            // 到时候需要添加分类
            String categoryCode = "1000";
            BpmnModel bpmnModel = modelService.getBpmnModel(model);
            // 租户信息
            String tenantId = model.getTenantId();


            // 必须指定文件后缀名，否则部署不成功
            Deployment deploy = repositoryService.createDeployment()
                    .name(model.getName())
                    .key(model.getKey())
                    .category(categoryCode)
                    .tenantId(tenantId)
                    .addBpmnModel(model.getKey() + ".bpmn", bpmnModel)
                    .deploy();

            ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).latestVersion().singleResult();
            /**
             * 实例流程创建统计数
             */
            String key = definition.getKey();
            boolean flow = redisUtil.hHasKey("flowable", key);
            if (!flow){
                // 不存在则创建统计数表
                redisUtil.hset("flowable", key, 0);
            }

            /**
             * 同步节点设置
             */
            new Thread(() -> {
                Process process = repositoryService.getBpmnModel(definition.getId()).getMainProcess();
                Collection<FlowElement> flowElements = process.getFlowElements();
                List<FlwExNodeset> elementVos = new ArrayList<>();
                for (Iterator<FlowElement> iter = flowElements.iterator(); iter.hasNext(); ) {
                    FlowElement element = iter.next();
                    if (element instanceof UserTask) {
                        FlwExNodeset vo = new FlwExNodeset();
                        vo.setElementId(element.getId());
                        vo.setElementName(element.getName());
                        vo.setElementInfo(element.getDocumentation());
                        vo.setProcessKey(definition.getKey());
                        vo.setVers(definition.getVersion());
                        List<SequenceFlow> outgoingFlows = ((UserTask) element).getOutgoingFlows();
                        a:for (SequenceFlow targetFlow : outgoingFlows){
                            FlowElement target = targetFlow.getTargetFlowElement();
                            if (target instanceof EndEvent){
                                vo.setNodeEnd(1);
                                break a;
                            }else if(target instanceof ExclusiveGateway){
                                List<SequenceFlow> nextOutgoingFlows = ((ExclusiveGateway) target).getOutgoingFlows();
                                for (SequenceFlow next : nextOutgoingFlows){
                                    FlowElement nextTarget = next.getTargetFlowElement();
                                    if (nextTarget instanceof EndEvent){
                                        vo.setNodeEnd(1);
                                        break a;
                                    }
                                }
                            }
                        }
                        List<SequenceFlow> incomingFlows = ((UserTask) element).getIncomingFlows();
                        for(SequenceFlow incomFlow : incomingFlows){
                            FlowElement source = incomFlow.getSourceFlowElement();
                            if (source instanceof StartEvent){
                                vo.setNodeStart(1);
                                break;
                            }
                        }
                        String assignee = ((UserTask) element).getAssignee() == null?"":((UserTask) element).getAssignee();
                        if("${initiator}".equals(assignee)){
                                vo.setNodeLoader(1);
                        }

                        elementVos.add(vo);
                    } else if (element instanceof StartEvent) {
                        FlwExNodeset vo = new FlwExNodeset();
                        vo.setElementId(element.getId());
                        vo.setElementName(element.getName() == null ? "开始" : element.getName());
                        vo.setElementInfo(element.getDocumentation());
                        vo.setProcessKey(definition.getKey());
                        vo.setVers(definition.getVersion());
                        elementVos.add(vo);
                    } else if (element instanceof EndEvent) {
                        FlwExNodeset vo = new FlwExNodeset();
                        vo.setElementId(element.getId());
                        vo.setElementName(element.getName() == null ? "关闭流程" : element.getName());
                        vo.setElementInfo(element.getDocumentation());
                        vo.setProcessKey(definition.getKey());
                        vo.setVers(definition.getVersion());
                        elementVos.add(vo);
                    }
                }

                elementVos.sort(new Comparator<FlwExNodeset>() {
                    @Override
                    public int compare(FlwExNodeset o1, FlwExNodeset o2) {
                        String s1 = o1.getElementId().substring(1);
                        String s2 = o2.getElementId().substring(1);

                        int diff = Integer.valueOf(s1) - Integer.valueOf(s2);

                        if (diff > 0) {
                            return 1;
                        } else if (diff < 0) {
                            return -1;
                        }
                        return 0;
                    }
                });

                iFlowableBpmnModelService.insertFlwNode(elementVos);

            }).start();
            returnVO = new ReturnVO(ReturnCode.SUCCESS, "流程模板" + deploy.getName() + "(" + deploy.getKey() + ")第" + model.getVersion() + "版署成功！", deploy.getKey());

        } catch (Exception e) {
            e.printStackTrace();
            returnVO = new ReturnVO(ReturnCode.FAIL, e.getMessage());
        }
        return returnVO;
    }

    /**
     * @Title: 更新流程模型
     * 此方法已废除
     */
    @Deprecated
    @PostMapping("/update/{modelId}")
    @ApiOperation("发布流程模型")
    @ApiImplicitParam(name = "modelId", value = "流程模板ID", dataType = "String")
    public ReturnVO<DeployElementVO> deploymentDefinition2(@PathVariable String modelId, @RequestBody HashMap<String, Object> values) {
        ReturnVO returnVo = null;
        try {
            Model oldModel = modelService.getModel(modelId);
            String bpmnXml = values.get("bpmnXml").toString();

            // 创建转换对象
            BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
            // XMLStreamReader读取XML资源
            XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
            StringReader stringReader = new StringReader(bpmnXml);
            XMLStreamReader xmlStreamReader = null;
            try {
                xmlStreamReader = xmlInputFactory.createXMLStreamReader(stringReader);
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
            // UserTaskXMLConverter类是我自定义的
            BpmnXMLConverter.addConverter(new UserTaskXMLConverter());
            // 把xml转换成BpmnModel对象
            BpmnModel bpmnModel2 = bpmnXMLConverter.convertToBpmnModel(xmlStreamReader);

            // BpmnJsonConverter类是我自定义的
            // 创建转换对象
            BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
            // 把BpmnModel对象转换成json
            JsonNode jsonNodes = bpmnJsonConverter.convertToJson(bpmnModel2);
            // 返回的json会被保存到数据库中


            String name = values.get("name").toString();
            String key = values.get("modelkey").toString().replaceAll(" ", "");
            String description = values.get("description") == null ? "" : values.get("description").toString();
            String isNewVersionString = values.get("newversion").toString();
            String newVersionComment = null;

            ModelKeyRepresentation modelKeyInfo = modelService.validateModelKey(oldModel, oldModel.getModelType(), key);
            if (modelKeyInfo.isKeyAlreadyExists()) {
                throw new BadRequestException("Model with provided key already exists " + key);
            }

            boolean newVersion = false;
            if (false) {
                newVersion = true;
                newVersionComment = values.get("comment").toString();
            } else {
                if (isNewVersionString != null) {
                    newVersion = "true".equals(isNewVersionString);
                    newVersionComment = values.get("comment").toString();
                }
            }
            UserMO userMO = new UserMO();
            userMO.setId(values.get("userId").toString());
            Model newModel = modelService.saveModel(oldModel.getId(), name, key, description, jsonNodes.toString(), newVersion,
                    newVersionComment, userMO);

            returnVo = new ReturnVO(ReturnCode.SUCCESS, "update success");

        } catch (Exception e) {
            e.printStackTrace();
            returnVo = new ReturnVO(ReturnCode.FAIL, e.getMessage());
        }
        return returnVo;
    }

    /**
     *  获取租户流程定义信息
     */
    @PutMapping("/deploy-list")
    public PagerModel<ModelExVO> queryDeployList(@RequestBody QueryDeployMO params) {
        String systemName = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
        if(systemName.equals("admin")){
            systemName = null;
        }
        params.setTenantId(systemName);
        Query query = params.getQuery();

        return iFlowableBpmnModelService.queryDeployList(params, query);
    }

    /**
     * 创建流程模型。
     * 此方法已废除不再维护
     */
    @Deprecated
    @PostMapping("/create")
    public ReturnVO<String> createModel(@RequestBody ModelExUser params) {
        ReturnVO returnVO = null;
        String systemName = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
        if(systemName.equals("admin")){
            systemName = null;
        }
        ModelRepresentation modelRepresentation = params.getCreateModel();
        modelRepresentation.setKey(modelRepresentation.getKey().replaceAll(" ", ""));
        try {
            checkForDuplicateKey(modelRepresentation);
        } catch (Exception e) {
            return new ReturnVO(ReturnCode.FAIL, e.getMessage());
        }
        String json = modelService.createModelJson(modelRepresentation);
        modelRepresentation.setTenantId(systemName);
        try {
//            Model model1 = modelService.createModel(modelRepresentation, json, user);
            Model newModel = iFlowableModelService.createModel(modelRepresentation, json, params.getUserId());
            String id = newModel.getId();
            returnVO = new ReturnVO(ReturnCode.SUCCESS, "创建成功！", id);
        } catch (Exception e) {
            returnVO = new ReturnVO(ReturnCode.FAIL, "创建失败");
        }

        return returnVO;
    }

    protected void checkForDuplicateKey(ModelRepresentation modelRepresentation) {
        ModelKeyRepresentation modelKeyInfo = modelService.validateModelKey(null, modelRepresentation.getModelType(), modelRepresentation.getKey());
        if (modelKeyInfo.isKeyAlreadyExists()) {
            throw new ConflictingRequestException("Provided model key already exists: " + modelRepresentation.getKey());
        }
    }

    /**
     * 获取流程的xml。
     * 此方法已废除不再维护
     */
    @Deprecated
    @GetMapping("/xmlStr/{modelId}")
    public ReturnVO<String> getXmlStr(@PathVariable String modelId) {
        ReturnVO returnVO = null;
        Model model = modelService.getModel(modelId);
        byte[] bytes = modelService.getBpmnXML(model);
        String s = null;
        try {
            s = new String(bytes, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        returnVO = new ReturnVO(ReturnCode.SUCCESS, "ok", s);
        return returnVO;
    }

    /**
     * 更新流程模型。
     * 此方法已废除不再维护
     */
    @Deprecated
    @PostMapping("/save")
    public ReturnVO saveModel(@RequestBody SaveModelMO params) {
        ReturnVO returnVo = null;
        Model oldModel = modelService.getModel(params.getModelId());
        // 创建转换对象
        BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        // XMLStreamReader读取XML资源
        XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
        StringReader stringReader = new StringReader(params.getXmlStr());
        XMLStreamReader xmlStreamReader = null;
        try {
            xmlStreamReader = xmlInputFactory.createXMLStreamReader(stringReader);
        } catch (XMLStreamException e) {
            e.printStackTrace();
        }

        // UserTaskXMLConverter类是我自定义的
        BpmnXMLConverter.addConverter(new UserTaskXMLConverter());
        // 把xml转换成BpmnModel对象
        BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xmlStreamReader);

        // BpmnJsonConverter类是我自定义的
        // 创建转换对象
        BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
        // 把BpmnModel对象转换成json
        JsonNode jsonNodes = bpmnJsonConverter.convertToJson(bpmnModel);
        JsonNode documentationNode = jsonNodes.get("properties").get("documentation");
        String documentation = "";
        if (documentationNode != null) {
            documentation = documentationNode.textValue();
        }
        ModelKeyRepresentation modelKeyInfo = modelService.validateModelKey(oldModel, oldModel.getModelType(), params.getKey());
        if (modelKeyInfo.isKeyAlreadyExists()) {
            //throw new BadRequestException("Model with provided key already exists " + params.getKey());
            return new ReturnVO(ReturnCode.FAIL, "Model with provided key already exists " + params.getKey());
        }
        try {
            Model newModel = iFlowableModelService.saveModel(oldModel.getId(), params.getName(), params.getKey(), documentation, jsonNodes.toString(), params.getNewVersion(),
                    params.getNewVersionComment(), params.getUserId());
            returnVo = new ReturnVO(ReturnCode.SUCCESS, "保存成功！");
        } catch (Exception e) {
            e.printStackTrace();
            returnVo = new ReturnVO(ReturnCode.FAIL, e.getMessage());
        }
        return returnVo;
    }
}
